package generic.net.server;

import generic.net.Peer;
import generic.net.Request;
import generic.net.Response.Status;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A Service is some distinct functionality provided by a server.
 *
 * @author Adam Scarr <scarr.adam@gmail.com>
 */
public class Object2ServiceAdapter implements Service {

    private Map<String, Method> methods = new HashMap<String, Method>();
    private String serviceName;
    private Object provider;
    private List<String> exclusions;

    public Object2ServiceAdapter(Object provider) {
        this.provider = provider;
        serviceName = provider.getClass().getSimpleName();

        introspect();
    }

    public Object2ServiceAdapter(String serviceName, Object provider) {
        this.provider = provider;
        this.serviceName = serviceName;

        introspect();
    }

    // Build a list of all public methods, they will be made available on request.
    public void introspect() {
        buildExclusions();

        try {
            BeanInfo bean = Introspector.getBeanInfo(provider.getClass());

            MethodDescriptor[] methodDescriptors = bean.getMethodDescriptors();

            for (MethodDescriptor methodDescriptor : methodDescriptors) {
                Method method = methodDescriptor.getMethod();

                if (!isExcluded(method.getName())) {
                    methods.put(method.getName(), method);
                }
            }

        } catch (IntrospectionException ex) {
            ex.printStackTrace();
        }
    }

    // We dont want any of objects methods being made available publicly.
    public void buildExclusions() {
        exclusions = new ArrayList<String>();
        try {
            BeanInfo bean = Introspector.getBeanInfo(Object.class);
            MethodDescriptor[] methodDescriptors = bean.getMethodDescriptors();

            for (MethodDescriptor methodDescriptor : methodDescriptors) {
                Method method = methodDescriptor.getMethod();

                exclusions.add(method.getName());
            }
        } catch (IntrospectionException ex) {
            ex.printStackTrace();
        }
    }

    public boolean isExcluded(String methodName) {
        return exclusions.contains(methodName);
    }

    public String getName() {
        return serviceName;
    }

    public void requestReceived(Peer peer, Request request, Server server) {
        Method method = methods.get(request.getRequestType());
        if (method != null) {
            try {
                Object result;
                if (request.getPayload() == null) {
                    result = method.invoke(provider);
                } else {
                    result = method.invoke(provider, request.getPayload());
                }
                peer.sendResponse(request, result);
            } catch (IllegalAccessException ex) {
                ex.printStackTrace();
                peer.sendResponse(request, Status.ACCESS_DENIED);
            } catch (IllegalArgumentException ex) {
                ex.printStackTrace();
                peer.sendResponse(request, Status.BAD_ARGUMENT);
            } catch (InvocationTargetException ex) {
                ex.printStackTrace();
            }
        } else {
            System.out.println("[Object2ServiceAdapter] No method found called '" + request.getRequestType() + "'");
            peer.sendResponse(request, Status.NO_SUCH_TYPE);
        }
    }
}

